package com.monochromebytes.mononet.virtual;

import java.nio.ByteBuffer;

/**
 * @author ted
 *
 */
public class SequenceNumber {

    public static final int  LENGTH    = Short.SIZE / 8;
    public static final int  MAX_VALUE = 0xFFFF;

    private final ByteBuffer buffer;

    public SequenceNumber() {
        this(ByteBuffer.allocate(LENGTH));
    }

    public SequenceNumber(ByteBuffer buffer) {
        assert (buffer.capacity() >= LENGTH); // TODO
        this.buffer = buffer.slice();
    }

    public SequenceNumber(int value) {
        this(ByteBuffer.allocate(LENGTH), value);
    }

    public SequenceNumber(ByteBuffer buffer, int value) {
        assert (buffer.capacity() >= LENGTH);
        assert (value > 0 && value <= MAX_VALUE);
        this.buffer = buffer;
        this.buffer.putShort(0, intToUnsignedshort(value));
    }

    public SequenceNumber(ByteBuffer buffer, SequenceNumber previous) {
        this(buffer, unsignedShortToInt(previous.buffer.getShort(0)) + 1);
    }

    public boolean isMoreRecentThan(SequenceNumber other) {
        int thisInt = unsignedShortToInt(this.buffer.getShort(0));
        int otherInt = unsignedShortToInt(other.buffer.getShort(0));

        return (thisInt > otherInt) && (thisInt - otherInt <= MAX_VALUE / 2)
                || (otherInt > thisInt) && (otherInt - thisInt > MAX_VALUE / 2);
    }

    public int getDistanceTo(SequenceNumber other) {
        int thisInt = unsignedShortToInt(this.buffer.getShort(0));
        int otherInt = unsignedShortToInt(other.buffer.getShort(0));
        
        int distance = otherInt - thisInt;
        int distanceAbsolute = Math.abs(distance);
        if (distanceAbsolute > MAX_VALUE / 2) {
            return distanceAbsolute - MAX_VALUE * Integer.signum(distance); 
        }
        return distance;
    }

    public void set(SequenceNumber other) {
        this.buffer.duplicate().put(other.buffer.duplicate());
    }
    
    public void increase(int value) {
        short newValue = (short) (this.buffer.getShort(0) + value);
        this.buffer.putShort(0, newValue);
    }

    @Override
    public String toString() {
        String string = Integer.toBinaryString(this.buffer.getShort(0));

        while (string.length() < LENGTH * 8) {
            string = "0" + string;
        }
        return string;
    }

    private static int unsignedShortToInt(short unsigned) {
        return (unsigned & 0xFFFF);
    }

    private static short intToUnsignedshort(int value) {
        return (short) value;
    }
    
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.buffer == null) ? 0 : this.buffer.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SequenceNumber other = (SequenceNumber) obj;
        if (this.buffer == null) {
            if (other.buffer != null)
                return false;
        }
        else if (!this.buffer.equals(other.buffer))
            return false;
        return true;
    }
}
